BSD kernel Hacking Guide Wed Jan 24 01:48:57 CET 2001 Ovo pisem dok citam BSD kode.Ako nadjete neku nepravilnost u gore napisanom textu ta nepravilnos se moze desiti da bude otklonjena kad dodjete do kraja moje beleznice.Nista nece biti ispravljano.Sve sto pisem bice onako kako sam ja shvatio citajuci kod!(bilo to pogresno ili ispravno) signed predator preedator(at)sendmail(dot)ru 1. SysCall na BSDu Sistemski poziv u userspacu se poziva sa int $0x80 a argumetni se nalaze na stacku.Stim sto je prvi argument nista drugo do JUNK. U %eax ide syscall koji je definisan u /usr/include/sys/syscall.h. Primer : movl $0x0,%ebx movl $23,%eax <--------setuid() push %ebx push %ebx <--------JUNK inace saved EIP kod call int $0x80 Ovaj kod generise syscall za setuid(0); U kernelu to je malo drugacije.Syscall u kernelu se definise u nizu koji se moze naci /usr/include/sys/sysent.h.Svi sistemski pozivi se definisu kao struktura od 2 argumenta koja se zove sysent -> : struct sysent { /* system call table */ int sy_narg; /* number of arguments */ sy_call_t *sy_call; /* implementing function */ }; sy_call_t je najobicniji tip kao int ili float definisan unutar kernela: typedef int sy_call_t __P((struct proc *, void *)); Syscallovi se drze u sysent nizu koji se moze naci u /sys/kern/init_sysent.c Struktura sysent ima dva argumenta a oni su : sy_narg i *sy_call; sy_narg je broj argumenata koji ce se predati datom syscall-u a sy_call je pointer na samu funkciju koja prima 2 parametra : struct proc *p (uobicajeno je oznaceno sa p u celom kernelu) naime ova structura proc sadrzi sve sto je potrebno da se jedan proces identifikuje u user_spaceu,tu se nalaze adrese koje on koristi,pid,uid,gid i sve sto jedan proces treba da ima na UNIX sistemu.Drugi argument void * je ustvari pointer na strukturu koja ce da sadrzi argumente te funkcije -> Uzmimo za primer setuid funkciju koja se nalazi u /sys/kern/kern_prot.c struct setuid_args { uid_t uid; }; int setuid(p, uap) struct proc *p; struct setuid_args *uap; { /*...*/ } Naime parametrima se pristupa preko tog void * koji je nista drugo do pointer na strukturu koju smo definisali.U gornjem primeru bih pristupio predanom UIDu sa uap->uid i to bi bila cela mudrost o definisanju sys_call-ova na FreeBSDu. Nisam siguran ali nagadjam da bi predati argumenti tj. struktura sa predatim argumentima trebala da ima isti broj argumenata koliko je definisano u sysent strukturi. Moguci razlozi koji mi padaju napamet su sledeci: 1. Ako je definisan veci broj argumenata a u sysent napisano da ima manje argumenata mozda ce onda kernel da preda funkciji recimo 2 argumenta a 3ci ce biti nasumice pokupljen sa stacka:o) 2. Ako definisemo manje nego sto je to u sysent mislim da ne bi trebalo da ima problema ali opet treba raditi onako kako je zapisano dok ne se ne uverim u suprotno. Eh pojavila se mala dilema koju jos nisam razresio.Sto bi rekli nasi konfuzija ali evo.Napisao sam da se syscall-u predaje proc * na trenutni proces:o) Ali malo dublje zaronivsi u kernel ispade da se trenutni proces definise sa struct proc curproc.Moguce,nisam siguran ali evo linija koda /sys/i386/i386/trap.c +1016 void syscall2(frame) struct trapframe frame; { caddr_t params; int i; struct sysent *callp; struct proc *p = curproc; u_quad_t sticks; int error; int narg; int args[8]; int have_mplock = 0; u_int code; /* ... */ /* prema ovome proc *p se dobija iz curproc-a. */ error = (*callp->sy_call)(p, args); /* Evoga i deo za poziv naseg syscall-a:o) Aha znaci u tom cucu grmi zec.Dobro sam shvatio. p je pointer na trenutni proces.Ok sad mi se razbistrilo.A args su argumenti koji se predaju funkciji. Zapravo predaje se pointer na adresu od args[8].Hmmm ako napisem neki syscall onda mogu preko asm-a da pristupam drugim delovima ove funkije (syscall2) recimo narg ili error ali ne vidim jos korist od ovog:o) Sta da kazem,jos sam zelen:o( No args[8] znaci da nas syscall moze da primi najvise 8 argumenata:o) Inace nadam se da nekog ne cudi kako se predaju stringovi u Cu.Naime sami stringovi se ne predaju na stack kao "predator" nego se preda adresa tog stringa drugim recima obican int na i386 je 4 byte znaci dovoljno da se prikaze neka adresa u flat memorijskom modelu:o) Ehhh flat memorijski model:o) */ switch (error) { case 0: /* * Reinitialize proc pointer `p' as it may be different * if this is a child returning from fork syscall. */ p = curproc; frame.tf_eax = p->p_retval[0]; frame.tf_edx = p->p_retval[1]; frame.tf_eflags &= ~PSL_C; break; /* ... */ Evo jos jedan neuspeli pokusaj brice da od bulje napravi lice. Naime jos jednom sam samom sebi potvrdio da se trenutni proces nalazi u proc *curproc,a da se *p predaje kao argument syscall-u:o) Sta znam mozda su BSD programeri odlucili da to uzmu kao neki svoj zastitni znak:o) Ko bi ga znao to p mu dodje kao proces,a i logicno je! Zar ne?hmmm da:o) I naravno Dzej. Ovaj switch..case iskaz sluzi da se stack frame poravna onako kako to dolikuje vracenoj greski.Predpostavljam da je struct trapframe frame; struktura koja se predaje syscall2 i da su na njoj sacuvani svi registri,argumenti i sta ti ja vec sve znam koji se koriste da se promeni task ili mozda kako da se vrati u pozivajuci program. Predavanje argumenta se vrsi tako sto se u int param; stavi adresa stacka+4 u param -> params = (caddr_t)frame.tf_esp + sizeof(int); Pa se onda sa copyin parametri iz userspace-a kopiraju u kernelspace sa (error = copyin(params, (caddr_t)args, (u_int)i))) { get_mplock(); have_mplock = 1; :o) Znaci sve mu se nakako radi sa copyin iz userspace u kernel.Sa mojim manje-vise iskustvom u Linux kernel presretanju i pisanju syscall-ova sam mislio da se samo stringovi trebaju kopirati sa copy_from_user() u neki buffer u kernelu ali ispada da se svi parametri kopiraju pre poziva syscall-a:o),pa uzavisnosti od od toga da li imamo string(njegovu adresu) ili samo neki broj mozemo posle u samom syscall-u da koristimo copyin(9), copyout(9),copyinstr(9),copystr(9),copy(9) funkcije za manipulaciju memorijom user vs kernel space.Za vise detalja o ovim funkcijama samo kucajte: #man 9 copy i to ce vam prikazati sve 4 funkcije koje sluze za kopiranje iz kernela u user space i obratno. Naime ja imam adresu od parametra na stacku i njih kad kopiram i predam syscall-u onda su tu.char * je nista drugo do int.Znaci kernel nam sve parametre prebaci iz userspace-a u kernelspace i mi sad samo treba da u zavisnosti da li nam treba jos nesto u toku syscall-a pozivamo copyin. Recimo funkcija koja iz userspace dovlaci neki string i isti testira sa recimo "predator" je vec u kernelu dobila sve parametre onih,args[8] a nama sad treba kompletan string na koji pokazuje neki argument.Znaci napravimo neki buffer i u njega kopiramo sa copyin ono nasta pokazuje preda argument?:o)Malo jednostavnije kernel nam je kopirao adresu u kernel space a mi sad samo treba da sa te adrese is userspace da prebacimo string, neku strukturu ili sta vec u kernel sa copy(9); :o) Mislim da sam sebi donkele razjasnio kako rade sys_call-ovi na FreeBSDu. Ehh uvek je bolje pogledati pa onda pricati i misliti:o) Kao sto sam predpostavio trapframe je struktura koja bi trebalo da pokazuje na parametre sacuvane na stacku kod poziva u /sys/i386/i386/exception.s +302 gde se kaze ovo->: IDTVEC(int0x80_syscall) subl $8,%esp /* skip over tf_trapno and tf_err */ pushal pushl %ds pushl %es pushl %fs mov $KDSEL,%ax /* switch to kernel segments */ mov %ax,%ds mov %ax,%es MOVL_KPSEL_EAX mov %ax,%fs movl $2,TF_ERR(%esp) /* sizeof "int 0x80" */ FAKE_MCOUNT(13*4(%esp)) MPLOCKED incl _cnt+V_SYSCALL call _syscall2 A nas trapframe izgleda ovako i nalazi se u /usr/include/machine/frame.h struct trapframe { int tf_fs; int tf_es; int tf_ds; int tf_edi; int tf_esi; int tf_ebp; int tf_isp; int tf_ebx; int tf_edx; int tf_ecx; int tf_eax; int tf_trapno; /* below portion defined in 386 hardware */ int tf_err; int tf_eip; int tf_cs; int tf_eflags; /* below only when crossing rings (e.g. user to kernel) */ int tf_esp; int tf_ss; }; Eno ih fs,es,ds koje smo sacuvali pa onda ide pushal koji pocinje od edi a zavrsava se na eax oa tf_trapno i tf_err je kako kaze kod : subl $8,%esp /* skip over tf_trapno and tf_err*/ Znaci neiskorisceni su ali nemora da znaci da se na njih nece pisati po povratku iz syscall2 ili u toku tog poziva:o) eip se cuva valjda usled call?!Ne znam kako bi se on naso tu:o) cs stvarno ne znam otkud tu.Mozda preko lcall ostaje na stacku ali ne znam nisam siguran jos.Ko zna mozda uskoro i saznam:o) Struktura proc izgleda ovako a nalazi se u : /usr/include/sys/proc.h +125 struct proc { TAILQ_ENTRY(proc) p_procq; /* run/sleep queue. */ LIST_ENTRY(proc) p_list; /* List of all processes. */ /* substructures: */ struct pcred *p_cred; /* Process owner's identity. */ struct filedesc *p_fd; /* Ptr to open files structure. */ struct pstats *p_stats; /* Accounting/statistics (PROC ONLY). */ struct plimit *p_limit; /* Process limits. */ struct vm_object *p_upages_obj;/* Upages object */ struct procsig *p_procsig; #define p_sigacts p_procsig->ps_sigacts #define p_sigignore p_procsig->ps_sigignore #define p_sigcatch p_procsig->ps_sigcatch #define p_ucred p_cred->pc_ucred #define p_rlimit p_limit->pl_rlimit int p_flag; /* P_* flags. */ char p_stat; /* S* process status. */ char p_pad1[3]; pid_t p_pid; /* Process identifier. */ LIST_ENTRY(proc) p_hash; /* Hash chain. */ LIST_ENTRY(proc) p_pglist; /* List of processes in pgrp. */ struct proc *p_pptr; /* Pointer to parent process. */ LIST_ENTRY(proc) p_sibling; /* List of sibling processes. */ LIST_HEAD(, proc) p_children; /* Pointer to list of children. */ struct callout_handle p_ithandle; /* * Callout handle for scheduling * p_realtimer. */ /* The following fields are all zeroed upon creation in fork. */ #define p_startzero p_oppid pid_t p_oppid; /* Save parent pid during ptrace. XXX */ int p_dupfd; /* Sideways return value from fdopen. XXX */ struct vmspace *p_vmspace; /* Address space. */ /* scheduling */ u_int p_estcpu; /* Time averaged value of p_cpticks. */ int p_cpticks; fixpt_t p_pctcpu; /* %cpu for this process during p_swtime */ void *p_wchan; /* Sleep address. */ const char *p_wmesg; /* Reason for sleep. */ u_int p_swtime; /* Time swapped in or out. */ u_int p_slptime; /* Time since last blocked. */ struct itimerval p_realtimer; /* Alarm timer. */ u_int64_t p_runtime; /* Real time in microsec. */ u_int64_t p_uu; /* Previous user time in microsec. */ u_int64_t p_su; /* Previous system time in microsec. */ u_int64_t p_iu; /* Previous interrupt time in usec. */ u_int64_t p_uticks; /* Statclock hits in user mode. */ u_int64_t p_sticks; /* Statclock hits in system mode. */ u_int64_t p_iticks; /* Statclock hits processing intr. */ int p_traceflag; /* Kernel trace points. */ struct vnode *p_tracep; /* Trac to vnode. */ sigset_t p_siglist; /* Signals arrived but not delivered. */ struct vnode *p_textvp; /* Vnode of executable. */ char p_lock; /* Process lock (prevent swap) count. */ u_char p_oncpu; /* Which cpu we are on */ u_char p_lastcpu; /* Last cpu we were on */ char p_rqindex; /* Run queue index */ short p_locks; /* DEBUG: lockmgr count of held locks */ short p_simple_locks; /* DEBUG: count of held simple locks */ unsigned int p_stops; /* procfs event bitmask */ unsigned int p_stype; /* procfs stop event type */ char p_step; /* procfs stop *once* flag */ unsigned char p_pfsflags; /* procfs flags */ char p_pad3[2]; /* padding for alignment */ register_t p_retval[2]; /* syscall aux returns */ struct sigiolst p_sigiolst; /* list of sigio sources */ int p_sigparent; /* signal to parent on exit */ sigset_t p_oldsigmask; /* saved mask from before sigpause */ int p_sig; /* for core dump/debugger XXX */ u_long p_code; /* for core dump/debugger XXX */ u_long p_code; /* for core dump/debugger XXX */ struct klist p_klist; /* knotes attached to this process */ /* End area that is zeroed on creation. */ #define p_endzero p_startcopy /* The following fields are all copied upon creation in fork. */ #define p_startcopy p_sigmask sigset_t p_sigmask; /* Current signal mask. */ stack_t p_sigstk; /* sp & on stack state variable */ u_char p_priority; /* Process priority. */ u_char p_usrpri; /* User-priority based on p_cpu and p_nice. */ char p_nice; /* Process "nice" value. */ char p_comm[MAXCOMLEN+1]; struct pgrp *p_pgrp; /* Pointer to process group. */ struct sysentvec *p_sysent; /* System call dispatch information. */ struct rtprio p_rtprio; /* Realtime priority. */ struct prison *p_prison; struct pargs *p_args; /* End area that is copied on creation. */ #define p_endcopy p_addr struct user *p_addr; /* Kernel virtual addr of u-area (PROC ONLY). */ struct mdproc p_md; /* Any machine-dependent fields. */ u_short p_xstat; /* Exit status for wait; also stop signal. */ u_short p_acflag; /* Accounting flags. */ struct rusage *p_ru; /* Exit information. XXX */ int p_nthreads; /* number of threads (only in leader) */ void *p_aioinfo; /* ASYNC I/O info */ int p_wakeup; /* thread id */ struct proc *p_peers; struct proc *p_leader; struct pasleep p_asleep; /* Used by asleep()/await(). */ void *p_emuldata; /* process-specific emulator state data */ }; Sto bi nasi rekli : :Ima muda da mi otpadnu dok ovo sve ne analiziram:o) struct sysentvec *p_sysent; /* System call dispatch information. */ Ova stuktura je definisana u /usr/include/sys/sysent.h : struct sysentvec { int sv_size; /* number of entries */ struct sysent *sv_table; /* pointer to sysent */ u_int sv_mask; /* optional mask to index */ int sv_sigsize; /* size of signal translation table */ int *sv_sigtbl; /* signal translation table */ int sv_errsize; /* size of errno translation table */ int *sv_errtbl; /* errno translation table */ int (*sv_transtrap) __P((int, int)); /* translate trap-to-signal mapping */ int (*sv_fixup) __P((register_t **, struct image_params *)); /* stack fixup function */ void (*sv_sendsig) __P((void (*)(int), int, struct __sigset *, u_long)); /* send signal */ char *sv_sigcode; /* start of sigtramp code */ int *sv_szsigcode; /* size of sigtramp code */ void (*sv_prepsyscall) __P((struct trapframe *, int *, u_int *, caddr_t *)); char *sv_name; /* name of binary type */ int (*sv_coredump) __P((struct proc *, struct vnode *, off_t)); /* function to dump core, or NULL */ int (*sv_imgact_try) __P((struct image_params *)); }; Ova je malo lakse? Hmmm ko bi ga znao:o) if (code == SYS_syscall) { /* * Code is first argument, followed by actual args. */ code = fuword(params); params += sizeof(int); } else if (code == SYS___syscall) { /*